#include "NSGAII.hh"

int NSGAII::RankMObjectiveComparator::mObjective = 0;

NSGAII::NSGAII(Population *current,
               ArrayList<Crossover> *crossovers,
               ArrayList<Migration> *migrations,
               ArrayList<Selection> *selections,
               ArrayList<Mutation> *mutations,
               float crossoverProbability) :MOEA(current,
               crossovers, migrations,selections, mutations,
               crossoverProbability) {}

NSGAII::NSGAII(): MOEA() {}


void NSGAII::generation() {
    ArrayList<Solution> *offsprings = makeOffsprings();

    ArrayList<Solution> *all = new ArrayList<Solution>(false);

    all->addAll(getCurrent()->getAll());
    all->addAll(offsprings);

    ArrayList<Solution> *processed = oneStep(all);

    getCurrent()->replace(processed);

    delete offsprings;
    delete all;
    delete processed;

}

void NSGAII::initialize() {
    ArrayList<Solution> * processed = oneStep(getCurrent()->getAll());
    getCurrent()->replace(processed);

    delete processed;

}

void NSGAII::finalize() {
    MOEA::finalize();
}


void NSGAII::show(ArrayList<Solution>* list) {
    cout << endl;
    for (int i = 0; i < list->length(); i++) {
        list->get(i)->send(cout);
        cout << endl;
    }

    cout << endl;
}


ArrayList<Solution> *NSGAII::makeOffsprings() {

    int size = getCurrent()->length();
    ArrayList<Solution>* next = new ArrayList<Solution>(true);

    while (next->length() < size) {

        Solution *selectedSolution = nextSelect()->select(getCurrent());
        if (Random::getInstance()->flip(crossoverProbability)) {

            Crossover *crossover = nextCrossover();
            ArrayList<Solution> *matingPool = nextSelect()->select(getCurrent(), crossover->matesExpected() - 1);
            matingPool->add(selectedSolution);

            ArrayList<Solution> *childs = crossover->apply(matingPool);

            next->addAll(childs);

            matingPool->setOwner(true);

            delete matingPool;
            delete childs;

        } else next->add(selectedSolution);
    }

    for (int i = 0; i < next->length(); i++) nextMutation()->apply(next->get(i));

    return next;
}

ArrayList<ArrayList<NSGAII::Rank> >* NSGAII::fastNonDominatedSort(ArrayList<NSGAII::Rank> *population) {

    ArrayList<ArrayList<NSGAII::Rank> >* fronts = new ArrayList<ArrayList<NSGAII::Rank> >(true);

    ArrayList<NSGAII::Rank> *temp = new ArrayList<NSGAII::Rank>(false);
    temp->addAll(population);

    ArrayList<ArrayList<NSGAII::Rank> >* dominated = new ArrayList<ArrayList<NSGAII::Rank> >(true);
    for (int i = 0; i < population->length(); i++)
        dominated->add(new ArrayList<NSGAII::Rank>(false));

    int *dominance_count = new int[population->length()];
    memset(dominance_count, 0, population->length() * sizeof(int));

    for (int i = 0; i < temp->length(); i++) {
        NSGAII::Rank *rank = temp->get(i);
        for (int j = 0; j < temp->length(); j++) {
            if (i != j) {
                NSGAII::Rank *otherRank = temp->get(j);
                if (rank->getSolution()->dominates(otherRank->getSolution())) {
                    dominance_count[j]++;
                    dominated->get(i)->add(otherRank);
                }
            }
        }
    }

    //set the rank value
    for (int i = 0; i < temp->length(); i++)
        population->get(i)->setValue(dominance_count[i] + 1);

    do {

        ArrayList<NSGAII::Rank> *currentFront = new ArrayList<NSGAII::Rank>(false);
        for (int i = 0; i < temp->length(); i++) {

            if (dominance_count[temp->get(i)->getIndex()] == 0) currentFront->add(temp->get(i));
        }

        //two passes to avoid piggy backing
        for (int i = 0; i < currentFront->length(); i++) {
            NSGAII::Rank *rank = currentFront->get(i);
            for (int j = 0; j < dominated->get(rank->getIndex())->length(); j++)
                dominance_count[dominated->get(rank->getIndex())->get(j)->getIndex()]--;

        }

        fronts->add(currentFront);
        temp->removeAll(currentFront);

    } while (!temp->empty());

    delete temp;
    delete dominated;
    delete[] dominance_count;

    return fronts;
}

void NSGAII::crowdingDistanceAssignment(ArrayList<NSGAII::Rank> *set) {

    for (int i = 0; i < set->length(); i++) set->get(i)->setDistance(0.0);

    for (int m = 0; m < Solution::getObjectivesLength(); m++) {

        NSGAII::RankMObjectiveComparator::mObjective = m;
        set->sort(NSGAII::RankMObjectiveComparator::mCompare);


        set->getFirst()->setDistance(INFINITE);
        set->getLast()->setDistance(INFINITE);

        for (int i = 1; i < set->length() - 1; i++) {

            if (set->get(i)->getDistance() != INFINITE)

                set->get(i)->setDistance(set->get(i)->getDistance() +
                                         set->get(i + 1)->getSolution()->getObjectiveValue(m) -
                                         set->get(i - 1)->getSolution()->getObjectiveValue(m));

        }

    }
}

ArrayList<Solution> *NSGAII::oneStep(ArrayList<Solution> *solutions) {

    ArrayList<NSGAII::Rank> *population = new ArrayList<NSGAII::Rank>(true);

    for (int i = 0; i < solutions->length(); i++) {

        NSGAII::Rank *rank = new NSGAII::Rank(solutions->get(i), 0, i);
        solutions->get(i)->evaluate();
        population->add(rank);
    }

    ArrayList<ArrayList<NSGAII::Rank> > *fronts = fastNonDominatedSort(population);

    ArrayList<NSGAII::Rank>* newPopulation = new ArrayList<NSGAII::Rank>(false);

    int n = getCurrent()->length();

    int i = 0;

    while (newPopulation->length() < n) {

        ArrayList<NSGAII::Rank> *front = fronts->get(i);
        crowdingDistanceAssignment(front);
        newPopulation->addAll(front);

        i++;
    }

    newPopulation->sort(NSGAII::RankMObjectiveComparator::compare);

    ArrayList<Solution> *nextSolutions = new ArrayList<Solution>();

    for (int i = 0; i < n; i++) {

        Solution *solution = solutions->get(newPopulation->get(i)->getIndex())->copy();

        solution->setFitness((double)n / ((double)newPopulation->get(i)->getValue() + 1));
        solution->setCrowding(newPopulation->get(i)->getDistance());

        nextSolutions->add(solution);
    }


    delete population;
    delete fronts;
    delete newPopulation;

    return nextSolutions;
}

